package org.korosoft.rusalad.syncnrun.command;

import org.korosoft.rusalad.syncnrun.messaging.Message;
import org.korosoft.rusalad.syncnrun.messaging.MessageProcessor;
import org.korosoft.rusalad.syncnrun.messaging.MessageRoute;
import org.korosoft.rusalad.syncnrun.messaging.Messenger;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Syncnrun "retrieve files" RPC command.
 *
 * @author Dmitry Korotkov
 * @since 1.0
 */
public class SyncRemoteDirCmd extends AbstractSyncCmd<SyncRemoteDirCmd.SyncRemoteDirAnswer> {

    private final UUID messagingRouteName;
    private final String sourcePath;

    private final transient File targetPath;
    private transient AtomicLong bytesSent = new AtomicLong();

    public SyncRemoteDirCmd(File targetPath, String sourcePath) {
        this.targetPath = targetPath;
        this.sourcePath = sourcePath;
        this.messagingRouteName = UUID.randomUUID();
    }

    public static class SyncRemoteDirAnswer implements Answer {
        public long bytesSent;

        public SyncRemoteDirAnswer(long bytesSent) {
            this.bytesSent = bytesSent;
        }

        @Override
        public String toString() {
            return "Remote dir sync done";
        }
    }

    @Override
    protected void initTransients() {
        super.initTransients();
        bytesSent = new AtomicLong();
    }

    @Override
    public Collection<UUID> getRouteNames() {
        return Collections.singletonList(messagingRouteName);
    }

    @Override
    public SyncRemoteDirAnswer execute(Messenger messenger) throws IOException {
        try {
            final LocalFilesInfo info = AbstractSyncCmd.prepareLocalFilesInfo(sourcePath);
            final CountDownLatch allFilesAreSent = new CountDownLatch(1);
            MessageRoute route = messenger.getRoute(messagingRouteName);
            route.setHotReceiver(new MessageProcessor() {
                @Override
                public void processMessage(MessageRoute route, Message message) {
                    if (!(message instanceof AbstractSyncCmd.FileHashListMsg)) {
                        throw new RuntimeException("Unexpected message: " + message.getClass().getName());
                    }
                    processHashListMessage(route, (FileHashListMsg) message, info.localFiles, info.localFilesPath, new Runnable() {
                        @Override
                        public void run() {
                            allFilesAreSent.countDown();
                        }
                    });
                }
            });
            route.put(new RemoteFileListMsg(info.localFiles, info.singleFileTransfer));
            allFilesAreSent.await();
            return new SyncRemoteDirAnswer(bytesSent.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void processMessageAtClient(MessageRoute route, Message message) {
        if (message instanceof RemoteFileListMsg) {
            try {
                RemoteFileListMsg msg = (RemoteFileListMsg) message;
                bytesSent.set(sendOutHashes(route, msg.sourceFiles, targetPath.getAbsolutePath(), msg.singleFileTransfer));
                return;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        throw new RuntimeException("Unexpected message: " + message);
    }

    @Override
    public String toString() {
        return String.format("Retrieve file / directory. Source path: %s.", targetPath);
    }

    private class RemoteFileListMsg implements Message {
        private final List<FileInfo> sourceFiles;
        private final boolean singleFileTransfer;

        private RemoteFileListMsg(List<FileInfo> sourceFiles, boolean singleFileTransfer) {
            this.sourceFiles = sourceFiles;
            this.singleFileTransfer = singleFileTransfer;
        }

        public String toString() {
            StringBuilder builder = new StringBuilder();
            for (FileInfo info : sourceFiles) {
                builder.append("\n\t");
                builder.append(info.toString());
            }
            return String.format("Remote file list message. Single file transfer: %s. Files: [%s].", singleFileTransfer, builder.toString());
        }
    }
}
